Modernizing Through Evolution Not Revolution - Guust Nieuwenhuis

October 05, 2022

My notes from Guust's presentation at CF Summit 2022

Average software application lifetime is 10 to 12 years.
(When Guust polled the room, many developers said the app they're working on is over 20 years old!)

10 years ago we got:
ColdBox MVC
Taffy
Node

Seen a lot during his career (especially with CF)
Lots of:
Spaghetti code
Outdated frameworks
Unsupported libs
Deprecated integrations
No tests
Undocumented apps
Last commits to libraries were over 10 years ago

We come across these things a lot
At any time these things could be a huge problem
These things make it hard to develop and maintain the software

So what's next?

Flights booking app engine
had many tenants, several really big websites running here
all pre-cloud architectures
performance issues
decided to rewrite from the ground up
kept adding new functionality to the "old" engine
development of the "new" engine trying to catch up
this was a financial disaster

"It's all CF's fault, let's rewrite it"

Tons of ways this could have been done better

"Rewrite code from scratch is the single worst strategic mistake that any software company can make"
-- Joel Spolsky, founder of Trello and Stack Overflow

It's easier to WRITE code than to READ code.
when you get to a new company, the on-boarding process requires reading a lot of code. That's a very slow, difficult process.
Have to get familiar with the codebase, can take many weeks/months.

Rewriting software
A few good reasons NOT to do that
A lot of the time, this is driven by "a feeling"
A feeling isn't sufficient enough to justify this
Especially when you're just walking out of a preso about a new tech
From a BUSINESS standpoint, this isn't enough to justify rewriting things
A rewrite also delays releasing the project
Rewriting will introduce more bugs and less functionality
Could potential re-introduce bugs in the new rewrite

If you compare software
Software is a pyramid
want to add function at the top, you have a whole foundation below
if you rewrite it, you have to rewrite that whole foundation first
That's terribly expensive, not a guaranteed success
We're not going back to the office tomorrow to rewrite from scratch

So what ARE we doing?

Story of the butterfly-
Web app written in CF, 12 years old, inherited from previous developers
client wanted new functionality
very little hand-over from the previous firm
all things we had to learn before proceeding
So how do we approach this?

Also wanted a mobile app on top of the existing app.

Needed an API
Build this on top of the existing codebase, as an add-on
Then client wanted big changes in the port, in the core of the application
Had a nice UI for the TIME it was built
But when it was built, mobile wasn't a variable
So none of this UI worked on mobile devices

"We're not changing the CORE part of the app, but we WILL add new code on NEXT to the core app, and we'll fix the UI in there"

Mobile REST API added next to the legacy app
Integrated the new front-end into the old front-end
Added a "new stuff" section of the menu, new UI in this new section, w/ modern standards, newer JS, etc. But left the pre-existing codebase alone
Everything shares the same database

Planned to migrate the data into a new model in the DB, but eventually decided just to leave "old" and "new" models side by side, will migrate them later when it's feasible.

In doing this, we heavily relied on feature flags

Another story: Cattle

Belgium company, not doing CF
Smalltalk for all their software

25 year old client/server app
needed to move to the cloud
new module developed
domain driven design
microservices architecture
events between "old" and "new"
implemented all w/ microservices
React.js

legacy backend stayed in Smalltalk w/ its own database
new module was separate, used React as front-end with its own db
syncro'd the DB between the 2.
only needed SOME data from the old system, so when that changed, sync'd it to the new database (and vice versa)

How to do this?

Various architecture approaches you can use

3 things you need to do

1. Case a reason WHY you're doing this
the reason is NOT driven by technical things typically
if you're reliant on a lib that's depreciated, then ok you have a tech reason
but modernizing needs to be driven from the business
adding new functionality, new module, big changes in existing functionality, etc.
Take that opportunity, look at it and refactor

2. Set a boundary on what you're going to add
don't start somewhere then after 2 weeks rewrite the whole thing because you kept touching other parts of the app, and kept bringing things along in the refactor, making the project larger, etc.

For example, if you're taking a shuttle from the hotel to the aport, refactor the "shuttle" part, but don't need to touch the rest of the User or Hotel business logic. Leave it be for now.

3. What is the strategy going to be for moving forward?

Strangler Fig Pattern --
Strangler plant wraps itself around another plant little by little, takes over everything over time. This works well in migrating an app little by little as well. Work incrementally, keep an eye on it, have immediate returns on the investments.

The business wants to INVEST but they want to have RETURN too. Don't want to wait 3 years for a total rewrite.

Strategies: Bubble Context

2 apps - legacy and new
use the same database

Anticorruption Layer (ACL)
What if you have a bad database design?
Still use a single DB but make sure the new app doesn't go directly into the OLD model, go into the new model -- use an Anticorruption layer (ACL)
ACL - translates between the old and mew models
don't want to corrupt the new system with the old model
can do similar things when dealing with external systems
over time, move things to the new data model and phase out the old model

still use the ACL in the legacy system, but first save everything in the new DB model
sychro over the 2 systems
can be very complex, headaches
but can go simple with things like a nightly cron job to do the syncin'g
if everything NEEDS to be real-time, it's more challenging. But do you REALLY need real-time data?

3. Event Queue
Any time something is in the queue, system picks it up and listens to it
Booking engines use this a lot
Queue can be used by a microservice - just pass it the things it needs (first name, last name, etc) and skip the rest.

Same habits, Same Mistakes! --

If you do this, make sure you change your habits.
Lots of time, "we're not doing that because the WHOLE app doesn't do it, so let's just stay the old way"
Over time this causes the same mistakes

Read more on these topics:
Eric Evans
Bounded Context
Domain Driven Design
Strangler Fig Pattern
Anti Corruption Layer
Event Drive Architecture
Microservices
Anti Patterns
Martin Fowler

One more example: When we DO rewrite the entire system

3 apps (1 in CF, 1 low-code, 1 something else)
Overlapping functionality
Consolidating the apps
Can say "pick 1 of the 3 and migrate the other 2 into that app"
But we're building a Minimum Viable Product
subset of existing functionality
there is a biz case to do this, to break into a NEW market with the MVP
Won't copy all the functionality to the new system

Business case drives a rewrite

We'll get a really fast ROI

After clients start using it, we can look at what we need to port over next, then get all of that ported, new functionality in the new system, etc.

(Some functionality we can just leave in the legacy system and skip it, users aren't using it any longer, etc.)